/**
 * LRU 文件存储，使用该 downloader 可以让下载的文件存储在本地，下次进入小程序后可以直接使用
 * 详细设计文档可查看 https://juejin.im/post/5b42d3ede51d4519277b6ce3
 */
const util = require('./util');
const sha1 = require('./sha1');

const SAVED_FILES_KEY = 'savedFiles';
const KEY_TOTAL_SIZE = 'totalSize';
const KEY_PATH = 'path';
const KEY_TIME = 'time';
const KEY_SIZE = 'size';

// 可存储总共为 6M，目前小程序可允许的最大本地存储为 10M
let MAX_SPACE_IN_B = 6 * 1024 * 1024;
let savedFiles = {};

export default class Dowloader {
    constructor() {
        // app 如果设置了最大存储空间，则使用 app 中的
        if (getApp().PAINTER_MAX_LRU_SPACE) {
            MAX_SPACE_IN_B = getApp().PAINTER_MAX_LRU_SPACE;
        }
        wx.getStorage({
            key: SAVED_FILES_KEY,
            success: function (res) {
                if (res.data) {
                    savedFiles = res.data;
                }
            },
        });
    }

    /**
     * 下载文件，会用 lru 方式来缓存文件到本地
     * @param {String} url 文件的 url
     */
    download (url, lru) {
        return new Promise((resolve, reject) => {
            if (!(url && util.isValidUrl(url))) {
                resolve(url);
                return;
            }
            const fileName = getFileName(url);
            if (!lru) {
                // 无 lru 情况下直接判断 临时文件是否存在，不存在重新下载
                wx.getFileInfo({
                    filePath: fileName,
                    success: () => {
                        resolve(url);
                    },
                    fail: () => {
                        if (util.isOnlineUrl(url)) {
                            downloadFile(url, lru).then((path) => {
                                resolve(path);
                            }, () => {
                                reject();
                            });
                        } else if (util.isDataUrl(url)) {
                            transformBase64File(url, lru).then(path => {
                                resolve(path);
                            }, () => {
                                reject();
                            });
                        }
                    },
                })
                return
            }

            const file = getFile(fileName);

            if (file) {
                if (file[KEY_PATH].indexOf('//usr/') !== -1) {
                    wx.getFileInfo({
                        filePath: file[KEY_PATH],
                        success () {
                            resolve(file[KEY_PATH]);
                        },
                        fail (error) {
                            console.error(`base64 file broken, ${JSON.stringify(error)}`);
                            transformBase64File(url, lru).then(path => {
                                resolve(path);
                            }, () => {
                                reject();
                            });
                        }
                    })
                } else {
                    // 检查文件是否正常，不正常需要重新下载
                    wx.getSavedFileInfo({
                        filePath: file[KEY_PATH],
                        success: (res) => {
                            resolve(file[KEY_PATH]);
                        },
                        fail: (error) => {
                            console.error(`the file is broken, redownload it, ${JSON.stringify(error)}`);
                            downloadFile(url, lru).then((path) => {
                                resolve(path);
                            }, () => {
                                reject();
                            });
                        },
                    });
                }
            } else {
                if (util.isOnlineUrl(url)) {
                    downloadFile(url, lru).then((path) => {
                        resolve(path);
                    }, () => {
                        reject();
                    });
                } else if (util.isDataUrl(url)) {
                    transformBase64File(url, lru).then(path => {
                        resolve(path);
                    }, () => {
                        reject();
                    });
                }
            }
        });
    }
}

function getFileName (url) {
    if (util.isDataUrl(url)) {
        const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(url) || [];
        const fileName = `${sha1.hex_sha1(bodyData)}.${format}`;
        return fileName;
    } else {
        return url;
    }
}

function transformBase64File (base64data, lru) {
    return new Promise((resolve, reject) => {
        const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
        if (!format) {
            console.error('base parse failed');
            reject();
            return;
        }
        const fileName = `${sha1.hex_sha1(bodyData)}.${format}`;
        const path = `${wx.env.USER_DATA_PATH}/${fileName}`;
        const buffer = wx.base64ToArrayBuffer(bodyData.replace(/[\r\n]/g, ""));
        wx.getFileSystemManager().writeFile({
            filePath: path,
            data: buffer,
            encoding: 'binary',
            success () {
                wx.getFileInfo({
                    filePath: path,
                    success: (tmpRes) => {
                        const newFileSize = tmpRes.size;
                        lru ? doLru(newFileSize).then(() => {
                            saveFile(fileName, newFileSize, path, true).then((filePath) => {
                                resolve(filePath);
                            });
                        }, () => {
                            resolve(path);
                        }) : resolve(path);
                    },
                    fail: (error) => {
                        // 文件大小信息获取失败，则此文件也不要进行存储
                        console.error(`getFileInfo ${path} failed, ${JSON.stringify(error)}`);
                        resolve(path);
                    },
                });
            },
            fail (err) {
                console.log(err)
            }
        })
    });
}

function downloadFile (url, lru) {
    return new Promise((resolve, reject) => {
        const downloader = url.startsWith('cloud://') ? wx.cloud.downloadFile : wx.downloadFile
        downloader({
            url: url,
            fileID: url,
            success: function (res) {
                if (res.statusCode !== 200) {
                    console.error(`downloadFile ${url} failed res.statusCode is not 200`);
                    reject();
                    return;
                }
                const {
                    tempFilePath
                } = res;
                wx.getFileInfo({
                    filePath: tempFilePath,
                    success: (tmpRes) => {
                        const newFileSize = tmpRes.size;
                        lru ? doLru(newFileSize).then(() => {
                            saveFile(url, newFileSize, tempFilePath).then((filePath) => {
                                resolve(filePath);
                            });
                        }, () => {
                            resolve(tempFilePath);
                        }) : resolve(tempFilePath);
                    },
                    fail: (error) => {
                        // 文件大小信息获取失败，则此文件也不要进行存储
                        console.error(`getFileInfo ${res.tempFilePath} failed, ${JSON.stringify(error)}`);
                        resolve(res.tempFilePath);
                    },
                });
            },
            fail: function (error) {
                console.error(`downloadFile failed, ${JSON.stringify(error)} `);
                reject();
            },
        });
    });
}

function saveFile (key, newFileSize, tempFilePath, isDataUrl = false) {
    return new Promise((resolve, reject) => {
        if (isDataUrl) {
            const totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0;
            savedFiles[key] = {};
            savedFiles[key][KEY_PATH] = tempFilePath;
            savedFiles[key][KEY_TIME] = new Date().getTime();
            savedFiles[key][KEY_SIZE] = newFileSize;
            savedFiles['totalSize'] = newFileSize + totalSize;
            wx.setStorage({
                key: SAVED_FILES_KEY,
                data: savedFiles,
            });
            resolve(tempFilePath);
            return;
        }
        wx.saveFile({
            tempFilePath: tempFilePath,
            success: (fileRes) => {
                const totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0;
                savedFiles[key] = {};
                savedFiles[key][KEY_PATH] = fileRes.savedFilePath;
                savedFiles[key][KEY_TIME] = new Date().getTime();
                savedFiles[key][KEY_SIZE] = newFileSize;
                savedFiles['totalSize'] = newFileSize + totalSize;
                wx.setStorage({
                    key: SAVED_FILES_KEY,
                    data: savedFiles,
                });
                resolve(fileRes.savedFilePath);
            },
            fail: (error) => {
                console.error(`saveFile ${key} failed, then we delete all files, ${JSON.stringify(error)}`);
                // 由于 saveFile 成功后，res.tempFilePath 处的文件会被移除，所以在存储未成功时，我们还是继续使用临时文件
                resolve(tempFilePath);
                // 如果出现错误，就直接情况本地的所有文件，因为你不知道是不是因为哪次lru的某个文件未删除成功
                reset();
            },
        });
    });
}

/**
 * 清空所有下载相关内容
 */
function reset () {
    wx.removeStorage({
        key: SAVED_FILES_KEY,
        success: () => {
            wx.getSavedFileList({
                success: (listRes) => {
                    removeFiles(listRes.fileList);
                },
                fail: (getError) => {
                    console.error(`getSavedFileList failed, ${JSON.stringify(getError)}`);
                },
            });
        },
    });
}

function doLru (size) {
    if (size > MAX_SPACE_IN_B) {
        return Promise.reject()
    }
    return new Promise((resolve, reject) => {
        let totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0;

        if (size + totalSize <= MAX_SPACE_IN_B) {
            resolve();
            return;
        }
        // 如果加上新文件后大小超过最大限制，则进行 lru
        const pathsShouldDelete = [];
        // 按照最后一次的访问时间，从小到大排序
        const allFiles = JSON.parse(JSON.stringify(savedFiles));
        delete allFiles[KEY_TOTAL_SIZE];
        const sortedKeys = Object.keys(allFiles).sort((a, b) => {
            return allFiles[a][KEY_TIME] - allFiles[b][KEY_TIME];
        });

        for (const sortedKey of sortedKeys) {
            totalSize -= savedFiles[sortedKey].size;
            pathsShouldDelete.push(savedFiles[sortedKey][KEY_PATH]);
            delete savedFiles[sortedKey];
            if (totalSize + size < MAX_SPACE_IN_B) {
                break;
            }
        }

        savedFiles['totalSize'] = totalSize;

        wx.setStorage({
            key: SAVED_FILES_KEY,
            data: savedFiles,
            success: () => {
                // 保证 storage 中不会存在不存在的文件数据
                if (pathsShouldDelete.length > 0) {
                    removeFiles(pathsShouldDelete);
                }
                resolve();
            },
            fail: (error) => {
                console.error(`doLru setStorage failed, ${JSON.stringify(error)}`);
                reject();
            },
        });
    });
}

function removeFiles (pathsShouldDelete) {
    for (const pathDel of pathsShouldDelete) {
        let delPath = pathDel;
        if (typeof pathDel === 'object') {
            delPath = pathDel.filePath;
        }
        if (delPath.indexOf('//usr/') !== -1) {
            wx.getFileSystemManager().unlink({
                filePath: delPath,
                fail (error) {
                    console.error(`removeSavedFile ${pathDel} failed, ${JSON.stringify(error)}`);
                }
            })
        } else {
            wx.removeSavedFile({
                filePath: delPath,
                fail: (error) => {
                    console.error(`removeSavedFile ${pathDel} failed, ${JSON.stringify(error)}`);
                },
            });
        }
    }
}

function getFile (key) {
    if (!savedFiles[key]) {
        return;
    }
    savedFiles[key]['time'] = new Date().getTime();
    wx.setStorage({
        key: SAVED_FILES_KEY,
        data: savedFiles,
    });
    return savedFiles[key];
}